package org.jeecg.modules.demo.DPT.controller;

import java.io.*;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.aliyun.dingtalkstorage_1_0.Client;
import com.aliyun.dingtalkstorage_1_0.models.*;
import com.aliyun.dingtalkworkflow_1_0.models.GetProcessInstanceResponseBody;
import com.aliyun.ocr_api20210707.models.RecognizeInvoiceResponse;
import com.aliyun.tea.TeaException;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.teautil.Common;
import com.aliyun.teautil.models.RuntimeOptions;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.jeecg.dingtalk.api.core.response.Response;
import com.kingdee.bos.webapi.sdk.K3CloudApi;
import com.taobao.api.ApiException;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.jeecg.common.api.dto.message.MessageDTO;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.config.TenantContext;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.constant.enums.MessageTypeEnum;
import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.util.JwtUtil;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.filter.SsrfFileTypeFilter;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.demo.DPT.entity.Ticket;
import org.jeecg.modules.demo.DPT.job.SynTicketJob;
import org.jeecg.modules.demo.DPT.service.ITicketService;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;

import org.jeecg.common.system.base.controller.JeecgController;
import org.jeecg.modules.demo.DptAccount.entity.DptAccount;
import org.jeecg.modules.demo.DptAccount.service.IDptAccountService;
import org.jeecg.modules.demo.PaymentRequest.entity.PaymentRequest;
import org.jeecg.modules.demo.PaymentRequest.job.SynPaymentReqJob;
import org.jeecg.modules.demo.PaymentTicket.entity.PaymentTicket;
import org.jeecg.modules.demo.util.DingTalkUtil;
import org.jeecg.modules.demo.util.TicketUtil;
import org.jeecg.modules.system.entity.SysThirdAppConfig;
import org.jeecg.modules.system.service.ISysThirdAppConfigService;
import org.jeecg.modules.system.service.impl.ThirdAppDingtalkServiceImpl;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.jeecg.common.aspect.annotation.AutoLog;
import org.apache.shiro.authz.annotation.RequiresPermissions;

import static org.jeecg.modules.demo.util.KingdeeUtil.convertExcelToMapList;

/**
 * @Description: 进项发票
 * @Author: jeecg-boot
 * @Date: 2025-05-14
 * @Version: V1.0
 */
@Tag(name = "进项发票")
@RestController
@RequestMapping("/Ticket/ticket")
@Slf4j
public class TicketController extends JeecgController<Ticket, ITicketService> {
    @Autowired
    private ITicketService ticketService;
    private CloseableHttpClient httpClient;
    private String token;

    /**
     * 分页列表查询
     *
     * @param ticket
     * @param pageNo
     * @param pageSize
     * @param req
     * @return
     */
    //@AutoLog(value = "进项发票-分页列表查询")
    @Operation(summary = "进项发票-分页列表查询")
    @GetMapping(value = "/list")
    public Result<IPage<Ticket>> queryPageList(Ticket ticket,
                                               @RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
                                               @RequestParam(name = "pageSize", defaultValue = "10") Integer pageSize,
                                               HttpServletRequest req) {
        QueryWrapper<Ticket> queryWrapper = QueryGenerator.initQueryWrapper(ticket, req.getParameterMap());
        Page<Ticket> page = new Page<Ticket>(pageNo, pageSize);
        IPage<Ticket> pageList = ticketService.page(page, queryWrapper);
        return Result.OK(pageList);
    }

    @Value(value = "${jeecg.path.upload}")
    private String uploadpath;


    @Autowired
    private ISysThirdAppConfigService appConfigService;
    @Autowired
    ThirdAppDingtalkServiceImpl dingtalkService;

    /**
     * 匹配对账单写入上传文件
     *
     * @param params 上传文件的地址
     * @return
     */
    @GetMapping(value = "/processAndDownload")
    public Result<?> processAndDownloadFile(String params, String dateRange, HttpServletRequest request, HttpServletResponse response) {

        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 提交异步任务
        executor.submit(() -> {
            try {
                process(params, dateRange, request, response);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("异步任务执行异常：" + e.getMessage());
                String receiver = JwtUtil.getUserNameByToken(request);
                // 租户id
                int tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
                String dingType = MessageTypeEnum.DD.getType();
                SysThirdAppConfig config = appConfigService.getThirdConfigByThirdType(tenantId, dingType);
                if (null != config) {
                    MessageDTO messageErr = new MessageDTO(receiver, receiver, "对账单匹配出错", "匹配过程出错，请联系管理员");
                    messageErr.setMsgType("text");
                    messageErr.setToAll(false);
                    dingtalkService.sendMessageResponse(messageErr, false);
                }
            }
        });
        return Result.OK("开始处理，完成后将发送钉钉消息");
    }

    public Result<?> process(String params, String dateRange, HttpServletRequest request, HttpServletResponse response) throws Exception {
        long startAll = System.currentTimeMillis();
        params = params.replace("..", "").replace("../", "");
        if (params.endsWith(SymbolConstant.COMMA)) {
            params = params.substring(0, params.length() - 1);
        }
        SsrfFileTypeFilter.checkDownloadFileType(params);
        String filePath = uploadpath + File.separator + params;
        File file = new File(filePath);
        if (!file.exists()) {
            response.setStatus(404);
            log.error("文件[" + params + "]不存在..");
            return Result.error("文件不存在");
        }

        K3CloudApi api = new K3CloudApi();
        List<Map<String, Object>> ExcelMap = List.of();
        JSONObject excelMapMerge = null;

        InputStream inputStream1 = new FileInputStream(filePath);
        List<Map<String, Object>> resultList = new ArrayList<>();
        // 临时参数：不跳过任何行，不设置表头，读取所有行作为原始文本
        ImportParams tempParams = new ImportParams();
        tempParams.setTitleRows(0);
        tempParams.setHeadRows(0);
        tempParams.setSheetNum(1); // 目标工作表
        tempParams.setStartSheetIndex(1);
        // 读取所有行到List<List<String>>（每行的单元格内容）
        List<Map> allRows = ExcelImportUtil.importExcel(
                inputStream1,
                Map.class,
                tempParams
        );
        int titleRowNum = 0; // 表头行号（最终需+1，因List从0开始）
// 定义表头的特征关键词（根据业务确定，如必须包含“序号”和“供应商名称”）
        String headerKeywords = "序号";

        for (int i = 0; i < allRows.size(); i++) {
            if (allRows.get(i).values().contains(headerKeywords)) {
                titleRowNum = i; // 转换为Excel的1-based行号
                break;
            }
            ;
        }
        inputStream1.close();

        // 租户id
        int tenantId = oConvertUtils.getInt(TenantContext.getTenant(), 0);
        String dingType = MessageTypeEnum.DD.getType();
        SysThirdAppConfig config = appConfigService.getThirdConfigByThirdType(tenantId, dingType);
//		  2. 文件处理逻辑（基于附件中的代码）
        // 2.1 转换Excel为Map列表
        ExcelMap = convertExcelToMapList(filePath, titleRowNum, 1, true, 1);
        String[] date = dateRange.split(",");
        String monthStart = date[0];
        String monthEnd = date[1];
        LocalDate dateStart = LocalDate.parse(monthStart);
        LocalDate dateEnd = LocalDate.parse(monthEnd);
        //将报表中的相同分录的数量合并
        excelMapMerge = new JSONObject();
        for (Map<String, Object> item : ExcelMap) {
            String itemDate = getformatterdate(item.get("送货日期（客户实际接收日期）").toString());
            LocalDate dateItem = LocalDate.parse(itemDate);

            // 2. 比较大小（3种核心方法）
            int isAfter = dateStart.compareTo(dateItem);
            int isBefore = dateItem.compareTo(dateEnd);
            if (!(isAfter <= 0 && isBefore <= 0)) {
                continue;
            }
            String purchaseno = item.get("采购订单号").toString().replaceAll(" ", "").replaceAll("\n", "");
            String materialNo = item.get("商品编码").toString().replaceAll(" ", "").replaceAll("\n", "");
            String index = item.get("序号").toString();
            BigDecimal materialCountInExcel = new BigDecimal(item.get("数量").toString());
            BigDecimal materialAmountInExcel = new BigDecimal(item.get("含税单价(RMB)").toString());
            JSONObject purchase = excelMapMerge.getJSONObject(purchaseno);
            if (purchase == null) {
                purchase = new JSONObject();
                JSONObject material = new JSONObject();
                JSONArray indexList = new JSONArray();
                indexList.add(index);
                material.put("indexList", indexList);
                material.put("materialCountInExcel", materialCountInExcel);
                material.put("materialCountInKD", BigDecimal.ZERO);
                material.put("materialAmountInExcel", materialAmountInExcel);
                purchase.put(materialNo, material);
                purchase.put("type", materialCountInExcel.compareTo(BigDecimal.ZERO) > 0 ? "销售" : "退货");
                excelMapMerge.put(purchaseno, purchase);
            } else {
                JSONObject material = purchase.getJSONObject(materialNo);
                if (material == null) {
                    material = new JSONObject();
                    JSONArray indexList = new JSONArray();
                    indexList.add(index);
                    material.put("indexList", indexList);
                    material.put("materialCountInExcel", materialCountInExcel);
                    material.put("materialCountInKD", BigDecimal.ZERO);
                    material.put("materialAmountInExcel", materialAmountInExcel);
                    purchase.put(materialNo, material);
                    purchase.put("type", materialCountInExcel.compareTo(BigDecimal.ZERO) > 0 ? "销售" : "退货");
                } else {
                    material.getJSONArray("indexList").add(index);
                    material.put("materialCountInExcel", material.getBigDecimal("materialCountInExcel").add(materialCountInExcel));
                }
            }
        }


        // 2.3 获取采购入库单明细 将金蝶的含税单价和数量放进excelMap中
        excelMapMerge.keySet();

// 1. 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(excelMapMerge.keySet().size()); // 根据实际情况调整线程数
        List<Future<Void>> futures = new ArrayList<>();
//			 遍历报表合并的数据
        for (String purchaseno : excelMapMerge.keySet()) {
            JSONObject purchase = excelMapMerge.getJSONObject(purchaseno);
            String type = purchase.getString("type");
            if (type.equals("退货")) {
                //退货订单只匹配单价是否正确
                processReturnOrder(purchase, api, monthEnd);
                continue;
            }
            futures.add(executor.submit(() -> {
                        //								 查询采购订单，如果为直运单不处理匹配，直接提示直运单
                        String jsonPurchase = "{\n" +
                                "\"FormId\": \"PUR_PurchaseOrder\",\n" +
                                "\"FieldKeys\": \"FBillTypeID \",\n" +
                                "\"FilterString\": \"FBillNo LIKE '" + purchaseno + "'\"\n" +
                                "}";
                        String PurchaseStr = String.valueOf(api.billQuery(jsonPurchase));
                        List<JSONObject> PurchaseStrs = JSONArray.parseArray(PurchaseStr, JSONObject.class);
                        String BillType = "";
                        if (PurchaseStrs.size() > 0) {
                            String FBillTypeID = PurchaseStrs.get(0).getString("FBillTypeID");
                            String jsonBillType = String.format("{\n" +
                                    "\"FormId\": \"BOS_BillType\",\n" +
                                    "\"FieldKeys\": \"FNAME \",\n" +
                                    "\"FilterString\": \"FBillTypeid LIKE '%s' AND FLOCALEID=2052\"\n" +
                                    "}", FBillTypeID);
                            String BillTypeStr = String.valueOf(api.billQuery(jsonBillType));
                            BillType = JSONArray.parseArray(BillTypeStr, JSONObject.class).get(0).getString("FNAME");
                        }

                        //直运订单不处理，直接返回
                        if (BillType.contains("直运")) {
                            purchase.forEach((materialNo, material) -> {
                                if (!materialNo.equals("type")) {
                                    ((JSONObject) material).put("remark", "直运单不处理匹配");
                                }
                            });
                            return null;
                        }
//								 查询收料通知单
                        String json1 = "{\n" +
                                "\"FormId\": \"PUR_ReceiveBill\",\n" +
                                "\"FieldKeys\": \"FID,FBillNo \",\n" +
                                "\"FilterString\": \"F_EC_SoureceNumber LIKE '%" + purchaseno + "%'  \",\n" +
                                "\"OrderString\": \"\",\n" +
                                "\"TopRowCount\": 0,\n" +
                                "\"StartRow\": 0,\n" +
                                "\"Limit\": 2000,\n" +
                                "\"SubSystemId\": \"\"\n" +
                                "}";

                        String ReceiveBillStr = String.valueOf(api.billQuery(json1));
                        List<JSONObject> ReceiveBills = JSONArray.parseArray(ReceiveBillStr, JSONObject.class);
                        for (JSONObject ReceiveBill : ReceiveBills) {
                            String FSBillId = ReceiveBill.getString("FID");
//						 用收料单源单号查询入库单明细
                            String json = String.format("{\n" +
                                    "    \"FormId\": \"STK_InStock\",\n" +
                                    "    \"FieldKeys\": \"FMaterialId,FMustQty,FRealQty,FBILLNO,FDocumentStatus,FTaxPrice,FDate,FSUPPLIERID,FPOORDERNO,FAPJOINAMOUNT \",\n" +
                                    "    \"FilterString\": \"FInStockEntry_Link_FSBillId = %S AND FSTABLENAME='T_PUR_ReceiveEntry' AND FDocumentStatus='C' AND FDATE>='%S' AND FDATE<='%S'\",\n" +
                                    "    \"OrderString\": \"\",\n" +
                                    "    \"TopRowCount\": 0,\n" +
                                    "    \"StartRow\": 0,\n" +
                                    "    \"Limit\": 2000,\n" +
                                    "    \"SubSystemId\": \"\"\n" +
                                    "}", FSBillId, monthStart, monthEnd);

//						 采购入库单明细数据
                            String InStockStr = String.valueOf(api.billQuery(json));
                            List<JSONObject> InStocks = JSONArray.parseArray(InStockStr, JSONObject.class);
                            if (InStocks.size() > 0) {

                                processInStock(InStocks, purchase, api, monthEnd);
                            }
                        }

//								 	查询采购订单直接下推的入库单
                        String json = "{\n" +
                                "    \"FormId\": \"STK_InStock\",\n" +
                                "    \"FieldKeys\": \"FMaterialId,FMustQty,FRealQty,FBILLNO,FDocumentStatus,FTaxPrice,FDate,FSUPPLIERID,FPOORDERNO,FAPJOINAMOUNT,FInStockEntry_Link_FSTableName \",\n" +
                                "    \"FilterString\": \"FSRCBillNo LIKE '%" + purchaseno + "%' AND FDocumentStatus='C' AND FDate>='" + monthStart + "' AND FDate<='" + monthEnd + "' AND FInStockEntry_Link_FSTableName= 't_PUR_POOrderEntry' \",\n" +
                                "    \"OrderString\": \"FDate desc\",\n" +
                                "    \"TopRowCount\": 0,\n" +
                                "    \"StartRow\": 0,\n" +
                                "    \"Limit\": 2000,\n" +
                                "    \"SubSystemId\": \"\"\n" +
                                "}";
//					 采购入库单明细数据
                        String InStockStrByFMATERIALIDS = String.valueOf(api.billQuery(json));
                        List<JSONObject> InStockByPurchase = JSONArray.parseArray(InStockStrByFMATERIALIDS, JSONObject.class);
                        if (InStockByPurchase.size() > 0) {
                            processInStock(InStockByPurchase, purchase, api, monthEnd);
                        }

                        return null;
                    }
                    //先用采购订单编号获取收料通知单
            ));

        }
        long start = System.currentTimeMillis();
        // 3. 等待所有任务完成
        for (Future<Void> future : futures) {
            future.get();
        }
        long end = System.currentTimeMillis();
        log.info("金蝶总耗时：" + (end - start) + "ms");
// 4. 关闭线程池
        executor.shutdown();

        // 3.1 匹配，将有出入的含税单价与数量标记起来
        for (String purchaseno : excelMapMerge.keySet()) {
            JSONObject purchase = excelMapMerge.getJSONObject(purchaseno);
            for (String materialNo : purchase.keySet()) {
                if (materialNo.equals("type")) {
                    continue;
                }
                JSONObject material = purchase.getJSONObject(materialNo);

                BigDecimal materialAmountInExcel = material.getBigDecimal("materialAmountInExcel");
                BigDecimal materialAmountInKD = material.getBigDecimal("materialAmountInKD");
                JSONArray instockList = material.getJSONArray("InstockList");
                BigDecimal materialCountInKD = material.getBigDecimal("materialCountInKD");
                BigDecimal materialCountInExcel = material.getBigDecimal("materialCountInExcel");
                String remark = material.getString("remark");
                if (materialAmountInKD == null) {
                    materialAmountInKD = BigDecimal.ZERO;
                }
                if (materialCountInKD == null) {
                    materialCountInKD = BigDecimal.ZERO;
                }
                String pre = "匹配正确没有问题";

                if (remark != null) {
                    pre = remark;
                } else if ((materialCountInKD.compareTo(materialCountInExcel) != 0 && !purchase.getString("type").equals("退货")) || materialAmountInKD.compareTo(materialAmountInExcel) != 0) {
                    pre = "不匹配，差异:";
                    if ((instockList == null || instockList.size() == 0) && !purchase.getString("type").equals("退货")) {
//							 不存在对应的入库单
                        pre += "不存在采购入库单";
                    } else {
                        if ((materialCountInKD.compareTo(materialCountInExcel) != 0 && !purchase.getString("type").equals("退货"))) {
                            pre = pre + "(入库数量" + materialCountInKD + "-发货数量" + materialCountInExcel + ")=" + materialCountInKD.subtract(materialCountInExcel).doubleValue();
                        }
                        if (materialAmountInKD.compareTo(materialAmountInExcel) != 0) {
                            pre = pre + "(入库单价" + materialAmountInKD + "-发货单价" + materialAmountInExcel + ")=" + materialAmountInKD.subtract(materialAmountInExcel).doubleValue();
                        }
                    }
                }
                // 获取该物料对应的所有行号
                JSONArray indexList = material.getJSONArray("indexList");
                for (int i = 0; i < indexList.size(); i++) {
                    String index = indexList.getString(i);
                    // 在result列表中找到对应行并添加差异信息
                    for (Map<String, Object> row : ExcelMap) {
                        if (row.get("序号").toString().equals(index)) {
                            row.put("差异信息", pre);
                            break;
                        }
                    }
                }
                if (instockList != null) {
                    for (Object instock : instockList) {
                        ((JSONObject) instock).put("materialAmountInKD", materialAmountInKD);
                        ((JSONObject) instock).put("materialCountInKD", materialCountInKD);
                        ((JSONObject) instock).put("materialAmountInExcel", materialAmountInExcel);
                        ((JSONObject) instock).put("materialCountInExcel", materialCountInExcel);
                        ((JSONObject) instock).put("result", pre);
                    }
                }
            }
        }

        //最后将差异信息写入到结果文件中
        InputStream inputStream = new FileInputStream(filePath);
        // 3. 生成结果文件流
        Workbook workbook = WorkbookFactory.create(inputStream);
//			XSSFWorkbook workbook = new XSSFWorkbook(inputStream);

        Sheet sheet = workbook.getSheetAt(1);

        // 3.1 添加差异信息列（P列）
        Row headerRow = sheet.getRow((titleRowNum == 0 ? titleRowNum : titleRowNum + 2));
        Cell pHeaderCell = headerRow.createCell(15);
        pHeaderCell.setCellValue("差异信息");

        // 3.2 写入差异数据
        for (Map<String, Object> rowData : ExcelMap) {
            String diffInfo = rowData.get("差异信息") == null ? null : rowData.get("差异信息").toString();
            BigDecimal amountwithoutTax = new BigDecimal(rowData.get("不含税金额").toString()).setScale(2, RoundingMode.HALF_UP);
            BigDecimal tax = new BigDecimal(rowData.get("税额").toString()).setScale(2, RoundingMode.HALF_UP);
            BigDecimal amountwithTax = new BigDecimal(rowData.get("含税金额RMB").toString()).setScale(2, RoundingMode.HALF_UP);
            if (amountwithoutTax.add(tax).compareTo(amountwithTax) != 0) {
                diffInfo = diffInfo == null ? "" : diffInfo;
                diffInfo += String.format("\n金额计算有误，不含税金额%s+税额%s 不等于含税金额%s", amountwithoutTax, tax, amountwithTax);
            }
            if (diffInfo != null) {
                int rowNum = (new BigDecimal(rowData.get("序号").toString())).intValue();
                Row row = sheet.getRow(rowNum + (titleRowNum == 0 ? titleRowNum : titleRowNum + 2));
                row.createCell(15).setCellValue(diffInfo);
            }
        }

        // 2. 创建工作表
        Sheet sheet1 = workbook.createSheet("采购入库单");
        for (int i = 0; i < 13; i++) {
            sheet1.setColumnWidth(i, 5000);
        }
        Row headerRowHead = sheet1.createRow(0);
        headerRowHead.createCell(0).setCellValue("入库单号");
        headerRowHead.createCell(1).setCellValue("供应商");
        headerRowHead.createCell(2).setCellValue("物料编号");
        headerRowHead.createCell(3).setCellValue("物料名称");
        headerRowHead.createCell(4).setCellValue("实收数量");
        headerRowHead.createCell(5).setCellValue("源单编号");
        headerRowHead.createCell(6).setCellValue("含税单价");
        headerRowHead.createCell(7).setCellValue("关联应付金额");
        headerRowHead.createCell(8).setCellValue("入库时间");
        headerRowHead.createCell(9).setCellValue("金蝶含税单价");
        headerRowHead.createCell(10).setCellValue("金蝶入库数量合计");
        headerRowHead.createCell(11).setCellValue("对账单含税单价");
        headerRowHead.createCell(12).setCellValue("对账单发货数量合计");
        headerRowHead.createCell(13).setCellValue("匹配结果");
        int rowNum = 1;
        excelMapMerge.keySet();
        for (String purchaseno : excelMapMerge.keySet()) {
            JSONObject purchase = excelMapMerge.getJSONObject(purchaseno);
            if (purchase.getString("type").equals("退货")) {
                continue;
            }
            purchase.remove("type");
            List allMaterialList = new ArrayList();
            purchase.forEach((materialNo, material) -> {
                if (((JSONObject) material).getJSONArray("InstockList") != null) {
                    allMaterialList.addAll(((JSONObject) material).getJSONArray("InstockList"));
                }
            });

            for (Object instock : allMaterialList) {
                Row headerRow1 = sheet1.createRow(rowNum++);
                headerRow1.createCell(0).setCellValue(((JSONObject) instock).getString("FBILLNO"));
                headerRow1.createCell(1).setCellValue(((JSONObject) instock).getString("supplierName"));
                headerRow1.createCell(2).setCellValue(((JSONObject) instock).getString("materialNoInKD"));
                headerRow1.createCell(3).setCellValue(((JSONObject) instock).getString("materialNameInKD"));
                headerRow1.createCell(4).setCellValue(((JSONObject) instock).getString("FRealQty"));
                headerRow1.createCell(5).setCellValue(((JSONObject) instock).getString("FPOORDERNO"));
                headerRow1.createCell(6).setCellValue(((JSONObject) instock).getString("FTaxPrice"));
                headerRow1.createCell(7).setCellValue(((JSONObject) instock).getString("FAPJOINAMOUNT"));
                headerRow1.createCell(8).setCellValue(((JSONObject) instock).getString("FDate"));
                headerRow1.createCell(9).setCellValue(((JSONObject) instock).getString("materialAmountInKD"));
                headerRow1.createCell(10).setCellValue(((JSONObject) instock).getString("materialCountInKD"));
                headerRow1.createCell(11).setCellValue(((JSONObject) instock).getString("materialAmountInExcel"));
                headerRow1.createCell(12).setCellValue(((JSONObject) instock).getString("materialCountInExcel"));
                headerRow1.createCell(13).setCellValue(((JSONObject) instock).getString("result"));
            }
        }
        // 3. 添加示例数据（可选）

        // 保存修改
        FileOutputStream fos = new FileOutputStream(filePath);
        workbook.write(fos);
        fos.close();

        long endAll = System.currentTimeMillis();
        log.info("方法总耗时：" + (endAll - startAll) + "ms");


        String mediaId = dingtalkService.uploadFile(filePath);
        String receiver = JwtUtil.getUserNameByToken(request);
        MessageDTO message = new MessageDTO(receiver, receiver, "对账单匹配完成", mediaId);
        message.setMsgType("file");
        message.setToAll(false);

        if (null != config) {
            //update-end---author:wangshuai ---date:20230224  for：[QQYUN-3440]钉钉、企业微信通过租户模式隔离 ------------
            Response<String> response1 = dingtalkService.sendMessageResponse(message, false);
            return Result.OK("发送消息成功");
        }
        return Result.ok("匹配完成，点击文件下载");
    }

    @GetMapping(value = "/getDateRangeFromFile")
    public Result<?> getDateRangeFromFile(String filepath) throws Exception {
        //获取表头起始位置
        filepath = filepath.replace("..", "").replace("../", "");
        if (filepath.endsWith(SymbolConstant.COMMA)) {
            filepath = filepath.substring(0, filepath.length() - 1);
        }
        SsrfFileTypeFilter.checkDownloadFileType(filepath);
        String filePath = uploadpath + File.separator + filepath;
        InputStream inputStream1 = new FileInputStream(filePath);
        List<Map<String, Object>> resultList = new ArrayList<>();
        // 临时参数：不跳过任何行，不设置表头，读取所有行作为原始文本
        ImportParams tempParams = new ImportParams();
        tempParams.setTitleRows(0);
        tempParams.setHeadRows(0);
        tempParams.setSheetNum(1); // 目标工作表
        tempParams.setStartSheetIndex(1);
        // 读取所有行到List<List<String>>（每行的单元格内容）
        List<Map> allRows = ExcelImportUtil.importExcel(
                inputStream1,
                Map.class,
                tempParams
        );
        int titleRowNum = 0; // 表头行号（最终需+1，因List从0开始）
// 定义表头的特征关键词（根据业务确定，如必须包含“序号”和“供应商名称”）
        String headerKeywords = "序号";

        for (int i = 0; i < allRows.size(); i++) {
            if (allRows.get(i).values().contains(headerKeywords)) {
                titleRowNum = i; // 转换为Excel的1-based行号
                break;
            }
            ;
        }
        inputStream1.close();
        //获取表头起始位置结束

        List<Map<String, Object>> maps = convertExcelToMapList(filePath, titleRowNum, 1, true, 1);
//		起始时间
        String date = maps.get(0).get("送货日期（客户实际接收日期）").toString();
        String startDate = getformatterdate(date);

//		终止时间
        String dateEnd = maps.get(maps.size() - 1).get("送货日期（客户实际接收日期）").toString();
        JSONObject monthStartAndEnd = DateUtils.getMonthStartAndEnd(getformatterdate(dateEnd), "yyyy-MM-dd");
        String endDate = monthStartAndEnd.getString("monthEnd");
        JSONArray result = new JSONArray();
        result.add(startDate);
        result.add(endDate);
        return Result.ok(result);
    }

    public String getformatterdate(String date) {
        String dateFormat = "yyyy-M-d";
        String formattedDate;
        if (date.contains("/")) {
            dateFormat = "yyyy/M/d";
            formattedDate = date;
        } else if (date.contains("-")) {
            dateFormat = "yyyy-M-d";
            formattedDate = date;
        } else {
            Date javaDate = DateUtil.getJavaDate(Double.parseDouble(date));
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            formattedDate = sdf.format(javaDate);
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        LocalDate inputDate = LocalDate.parse(formattedDate, formatter);
        String result = inputDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        return result;
    }


    private void processInStock(List<JSONObject> InStocks, JSONObject purchase, K3CloudApi api, String monthEnd) throws Exception {

        String FSUPPLIERID = InStocks.get(0).getString("FSUPPLIERID");

        //						 供应商名称
        String supplieridJson = String.format("{\n" +
                "    \"FormId\": \"BD_Supplier\",\n" +
                "    \"FieldKeys\": \" FName\",\n" +
                "    \"FilterString\": \"FSupplierId=%S \"\n" +
                "}", FSUPPLIERID);
        String supplier = String.valueOf(api.billQuery(supplieridJson));
        String supplierName = JSONArray.parseArray(supplier, JSONObject.class).get(0).getString("FName");
        //										 每一条入库单明细
        for (JSONObject InStock : InStocks) {
            String materialId = InStock.getString("FMaterialId");
            //					String FMustQty = InStock.getString("");
            BigDecimal FRealQty = InStock.getBigDecimal("FRealQty");
            BigDecimal FAPJOINAMOUNT = InStock.getBigDecimal("FAPJOINAMOUNT");
            String FBILLNO = InStock.getString("FBILLNO");
            String FTaxPrice = InStock.getString("FTaxPrice");

            //							 查询物料编号和名称
            String materialjson = String.format("{\n" +
                    "    \"FormId\": \"BD_MATERIAL\",\n" +
                    "    \"FieldKeys\": \"FNUMBER,FName\",\n" +
                    "    \"FilterString\": \"FMATERIALID = %S\",\n" +
                    "    \"OrderString\": \"\",\n" +
                    "    \"TopRowCount\": 0,\n" +
                    "    \"StartRow\": 0,\n" +
                    "    \"Limit\": 2000,\n" +
                    "    \"SubSystemId\": \"\"\n" +
                    "}", materialId);
            String materialStr = api.billQuery(materialjson);
            List<JSONObject> materials = JSONArray.parseArray(materialStr, JSONObject.class);
            String materialNoInKD = materials.get(0).getString("FNUMBER");
            String materialNameInKD = materials.get(0).getString("FName");

            InStock.put("materialNoInKD", materialNoInKD);
            InStock.put("materialNameInKD", materialNameInKD);
            InStock.put("supplierName", supplierName);
            //获取到的分录在上传报表中存在对应的物料，数量合并，设置含税单价
            if (purchase.getJSONObject(materialNoInKD) != null) {
                //报表对应的物料设置金蝶单价
                purchase.getJSONObject(materialNoInKD).put("materialAmountInKD", new BigDecimal(FTaxPrice));
                //报表对应的物料设置金蝶数量叠加
                BigDecimal materialCountInKD = purchase.getJSONObject(materialNoInKD).getBigDecimal("materialCountInKD");
                purchase.getJSONObject(materialNoInKD).put("materialCountInKD", materialCountInKD.add(FRealQty));
                if (purchase.getJSONObject(materialNoInKD).getJSONArray("InstockList") == null) {
                    JSONArray instockList = new JSONArray();
                    instockList.add(InStock);
                    purchase.getJSONObject(materialNoInKD).put("InstockList", instockList);
                } else {
                    purchase.getJSONObject(materialNoInKD).getJSONArray("InstockList").add(InStock);
                }

                //											 关联应付金额
                if (FAPJOINAMOUNT.compareTo(BigDecimal.ZERO) > 0) {
                    //												 入库单已经下推了应付
                    purchase.getJSONObject(materialNoInKD).put("remark", "已经下推了应付单，不处理匹配");
                }
            }
        }
    }

    private void processReturnOrder(JSONObject purchase, K3CloudApi api, String monthEnd) throws Exception {
        for (String materialNo : purchase.keySet()) {
            if (materialNo.equals("type")) {
                continue;
            }
            JSONObject meterial = purchase.getJSONObject(materialNo);
            String materialjson = String.format("{\n" +
                    "    \"FormId\": \"BD_MATERIAL\",\n" +
                    "    \"FieldKeys\": \"FMATERIALID,FNUMBER,FName\",\n" +
                    "    \"FilterString\": \"FNUMBER = '%S'\"\n" +
                    "}", materialNo);
//					 采购入库单明细数据
            String InStockStr = String.valueOf(api.billQuery(materialjson));
            List<JSONObject> InStocks = JSONArray.parseArray(InStockStr, JSONObject.class);
            String FMATERIALIDS = InStocks.stream().map(jsonObject -> jsonObject.getString("FMATERIALID")).collect(Collectors.joining(","));
            
            // 如果FMATERIALIDS为空，设置备注信息
            if (FMATERIALIDS == null || FMATERIALIDS.isEmpty()) {
                meterial.put("remark", "物料列表不存在该物料");
                continue;
            }
            
            String json = String.format("{\n" +
                    "    \"FormId\": \"STK_InStock\",\n" +
                    "    \"FieldKeys\": \"FMaterialId,FTaxPrice,FDate,FSUPPLIERID,FPOORDERNO,FAPJOINAMOUNT \",\n" +
                    "    \"FilterString\": \"FMATERIALID IN (%S) AND FDocumentStatus='C' AND FDATE<='%S'\",\n" +
                    "    \"OrderString\": \"FDate desc\",\n" +
                    "    \"TopRowCount\": 0,\n" +
                    "    \"StartRow\": 0,\n" +
                    "    \"Limit\": 2000,\n" +
                    "    \"SubSystemId\": \"\"\n" +
                    "}", FMATERIALIDS, monthEnd);
//					 采购入库单明细数据
            String InStockStrByFMATERIALIDS = String.valueOf(api.billQuery(json));
            List<JSONObject> InStockByFMATERIALIDS = JSONArray.parseArray(InStockStrByFMATERIALIDS, JSONObject.class);
            if (InStockByFMATERIALIDS.size() > 0) {
                String FTaxPrice = InStockByFMATERIALIDS.get(0).getString("FTaxPrice");
                meterial.put("materialAmountInKD", FTaxPrice);
            } else {
                meterial.put("remark", "该型号在系统查询不到采购价格，以供应商为准");
            }
        }

    }


    @GetMapping(value = "/updateTicket")
    public Result<String> updateTicket(Ticket ticket,
                                       HttpServletRequest req) {
        QueryWrapper<Ticket> queryWrapper = QueryGenerator.initQueryWrapper(ticket, req.getParameterMap());

        return Result.OK("");
    }

    @Autowired
    SynPaymentReqJob reqJob;

    /**
     * 根据审批结果更新发票状态
     *
     * @param parm
     * @return
     */
    @PostMapping(value = "/updateTicketStatus")
    public Result<String> updateTicketStatus(@RequestBody JSONObject parm) throws ApiException {
        String auditResult = parm.getString("AuditResult");
        String checkResult = parm.getString("checkResult");
        String type = parm.getString("type");
        String instid = parm.getString("instid");
        String updateStatusString;
        if (auditResult.equals("同意")) {
            updateStatusString = "2";
            //同步付款审批单

            PaymentRequest request = reqJob.saveSynPaymentReq(instid);
        } else if (auditResult.equals("发起")) {
            updateStatusString = "1";
        } else {
            updateStatusString = "0";
        }
        String[] tickets = checkResult.split(";");
        String regex = "发票号码：(\\d+)";
        Pattern pattern = Pattern.compile(regex);
        for (String ticket : tickets) {
//		 	提取发票号码
            String invoiceNumber = extractInvoiceNumber(ticket, pattern);
            if (!invoiceNumber.isEmpty()) {
                Ticket fphm = ticketService.query().eq("fphm", invoiceNumber).one();
                if (fphm != null) {
                    fphm.setStatus(updateStatusString);
                    if (auditResult.equals("发起")) {
                        fphm.setProjecttype(type);
                        GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult oaInstDetail = util.getOAInstDetail(instid);
                        String businessId = oaInstDetail.getBusinessId();
                        fphm.setOanumber(businessId);
                    }
                    ticketService.updateById(fphm);
                }
            }
        }
        log.info("审批流" + auditResult + ":更新成功,发票内容：" + checkResult);
        return Result.OK("审批流" + auditResult + ":更新成功,发票内容：" + checkResult);
    }

    /**
     * 根据审批实例id获取发票并统计发票金额
     *
     * @param parm
     * @return
     */
    @GetMapping(value = "/checkOAAmount")
    public Result<JSONObject> checkOAAmount(@RequestBody JSONObject parm) throws ApiException {
        String instid = parm.getString("instid");
        BigDecimal totalAmount = BigDecimal.ZERO;
        BigDecimal totalBySelf = BigDecimal.ZERO;
        JSONObject resultJsonObj = new JSONObject();
        GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResult oaInstDetail = util.getOAInstDetail(instid);
        if (oaInstDetail != null) {
            List<GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues> formComponentValues = oaInstDetail.getFormComponentValues();
            for (GetProcessInstanceResponseBody.GetProcessInstanceResponseBodyResultFormComponentValues formComponentValue : formComponentValues) {
                String name = formComponentValue.getName();
                if (name != null && name.equals("报销明细")) {
                    String value = formComponentValue.getValue();
                    JSONArray objects = JSONArray.parseArray(value);
                    for (Object object : objects) {
                        JSONObject detail = (JSONObject) object;
                        for (Object rowValue : detail.getJSONArray("rowValue")) {
                            String label = ((JSONObject) rowValue).getString("label");
                            if (label.equals("发票")) {
                                JSONArray invoiceList = ((JSONObject) rowValue).getJSONObject("extendValue").getJSONArray("invoiceList");
                                if (invoiceList != null) {
                                    for (Object invoice : invoiceList) {
                                        String formatInvoiceAmount = ((JSONObject) invoice).getString("formatInvoiceAmount");
                                        BigDecimal invoiceAmount = new BigDecimal(formatInvoiceAmount);
                                        totalAmount = totalAmount.add(invoiceAmount);
                                    }
                                }
                            }
                        }
                    }
                }
                if (name != null && name.equals("总金额")) {
                    totalBySelf = new BigDecimal(formComponentValue.getValue());
                }
            }
        }

        if (totalAmount.compareTo(BigDecimal.ZERO) == 0) {
//			 无发票
            resultJsonObj.put("checkResult", "通过");
            resultJsonObj.put("checkResultCode", 0);
            return Result.OK(resultJsonObj);
        } else {
            resultJsonObj.put("checkResult", totalAmount.compareTo(totalBySelf) == 0 ? "通过" : "不通过");
            resultJsonObj.put("checkResultCode", totalAmount.compareTo(totalBySelf));
            log.info(resultJsonObj.toString());
            return Result.OK(resultJsonObj);
        }
    }


    public static String extractInvoiceNumber(String input, Pattern pattern) {
        Matcher matcher = pattern.matcher(input);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return "";
        }
    }

    @Autowired
    IDptAccountService dptAccountService;
    @Autowired
    SynTicketJob synTicketJob;
    @Autowired
    TicketUtil aliyunOCR;

    private String processAtte(Object atte) throws Exception {
        String result = null;
        try {

            String spaceId = ((JSONObject) atte).getString("spaceId");
            String fileId = ((JSONObject) atte).getString("fileId");
            String fileName = ((JSONObject) atte).getString("fileName");
//			 授权
            AddPermissionResponse addPermissionResponse = storageClient.addPermissionWithOptions(spaceId, fileId, addPermissionRequest, addPermissionHeaders, new RuntimeOptions());
            if (!addPermissionResponse.getBody().success) {
                log.error("附件:" + fileName + " 添加权限失败");
            }
//			 获取下载链接
            GetFileDownloadInfoResponse attchmentDownloadInfo = getAttchmentDownloadInfo(spaceId, fileId, token, storageClient, getFileDownloadInfoHeaders, getFileDownloadInfoRequest);
            if (attchmentDownloadInfo != null) {
                GetFileDownloadInfoResponseBody.GetFileDownloadInfoResponseBodyHeaderSignatureInfo headerSignatureInfo = attchmentDownloadInfo.getBody().getHeaderSignatureInfo();
                String url = headerSignatureInfo.getResourceUrls().get(0);
                Map<String, String> headers = headerSignatureInfo.getHeaders();
                HttpURLConnection connection = TicketUtil.getFileInputStreamWithoutClosing(url, headers);
                try {
                    InputStream inputStream = connection.getInputStream();
                    String data = aliyunOCR.getInvoiceByOCR(inputStream);
                    JSONObject ocrdata = JSONObject.parseObject(data).getJSONObject("data");
                    String invoiceNumber = ocrdata.getString("invoiceNumber");
//					 String invoiceCode = JSONObject.parseObject(data).getJSONObject("data").getString("invoiceCode");
//					 if(!invoiceCode.isEmpty()){
//						 invoiceNumber=invoiceCode+invoiceNumber;
//					 }
                    Ticket fphm = ticketService.query().eq("fphm", invoiceNumber).one();
                    if (fphm != null) {
                        String status = fphm.getStatus();
                        String statusTip = status.equals("0") ? "校验通过" : status.equals("1") ? "正在使用，请检查！" : "已报销";
                        result = (fileName + "  (发票号码：" + invoiceNumber + ") " + statusTip + "");
                    } else {
                        String invoicedate = ocrdata.getString("invoiceDate");
                        String purchaserName = ocrdata.getString("purchaserName");
                        DptAccount dptAccount = dptAccountService.query().eq("corp_name", purchaserName).one();
                        if (dptAccount == null) {
                            result = (fileName + "  (发票号码：" + invoiceNumber + ") 识别完成但校验失败，非公司发票。");
                            return result;
                        }
                        String spid = dptAccount.getSpid();
                        String token = dptAccount.getToken();
                        // 格式化今天日期
                        SimpleDateFormat sdfAli = new SimpleDateFormat("yyyy年MM月dd日");
                        SimpleDateFormat outputFormatter = new SimpleDateFormat("yyyy-MM-dd");
                        Date date = sdfAli.parse(invoicedate);
                        String invoicedateDate = outputFormatter.format(date);
                        // 获取当前日期
                        Calendar calendar = Calendar.getInstance();
                        // 格式化今天日期
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                        String todayStr = sdf.format(calendar.getTime());

                        log.info("校验失败，尝试从电票通发票池检索(发票号码：" + invoiceNumber + ") " + "发票查询时间：" + invoicedateDate + "-" + todayStr);

                        JSONObject synTicket = synTicketJob.getSynTicket(token, 1, 1000, spid, invoicedateDate, todayStr);
                        if (synTicket.getString("Result").equals("0")) {
                            log.info(" 发票号码：" + invoiceNumber + " 查询电票通出错");
                            log.info(synTicket.toJSONString());
                        } else {
                            JSONArray rows = synTicket.getJSONArray("rows");
                            log.info("时间范围内查询到数量" + rows.size());
                            for (Object row : rows) {
                                String fphmInDPTPool = ((JSONObject) row).getString("FPHM");
                                if (fphmInDPTPool.contains(invoiceNumber)) {
                                    log.info(invoiceNumber + "匹配成功，开始同步到本地数据");
                                    fphm = synTicketJob.saveTicket(row);
                                    String status = fphm.getStatus();
                                    String statusTip = status.equals("0") ? "校验通过" : status.equals("1") ? "正在使用，请检查！" : "已报销";
                                    result = (fileName + "  (发票号码：" + invoiceNumber + ") " + statusTip + "");
                                    return result;
                                }
                            }
                        }
                        result = (fileName + "  (发票号码：" + invoiceNumber + ") 校验失败");
                    }
                } catch (Exception _error) {
                    TeaException error = new TeaException(_error.getMessage(), _error);
                    // 此处仅做打印展示，请谨慎对待异常处理，在工程项目中切勿直接忽略异常。
                    // 错误 message
                    log.error(error.getMessage());
                    log.error(_error.toString());

                    Common.assertAsString(error.message);
                    result = (fileName + " 识别失败！");
                } finally {
                    connection.disconnect();
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return result;
    }

    private Client storageClient;
    private AddPermissionHeaders addPermissionHeaders;
    private AddPermissionRequest addPermissionRequest;
    private GetFileDownloadInfoHeaders getFileDownloadInfoHeaders;
    private GetFileDownloadInfoRequest getFileDownloadInfoRequest;
    @Autowired
    DingTalkUtil util;

    /**
     * 获取发票校验信息
     *
     * @param ticket
     * @return
     * @throws Exception
     */
    @PostMapping(value = "/fromOA")
    public Result<String> fromOA(@RequestBody JSONObject ticket) throws Exception {
        long startTimeMillis = System.currentTimeMillis();
//		获取附件列表
        String atteString = ticket.getString("atte");
        JSONArray attes = JSONArray.parseArray(atteString);
        if (attes == null) {
            return Result.ok("");
        }
        String token = util.getAccessToken();
        String unionId = "zq3RFJ5p3yURjajCC1OplwiEiE";
        this.storageClient = createStorageClient();

        this.addPermissionHeaders = new AddPermissionHeaders();
        addPermissionHeaders.xAcsDingtalkAccessToken = token;
        AddPermissionRequest.AddPermissionRequestMembers members0 = new AddPermissionRequest.AddPermissionRequestMembers()
                .setType("USER")
                .setId(unionId);
        this.addPermissionRequest = new AddPermissionRequest()
                .setUnionId(unionId)
                .setRoleId("MANAGER")
                .setMembers(Arrays.asList(
                        members0
                ));
        this.getFileDownloadInfoHeaders = new GetFileDownloadInfoHeaders();
        this.getFileDownloadInfoHeaders.xAcsDingtalkAccessToken = token;
        this.getFileDownloadInfoRequest = new GetFileDownloadInfoRequest()
                .setUnionId(unionId);
        ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
        ExecutorService executor = Executors.newFixedThreadPool(4);

        for (Object atte : attes) {
            executor.submit(() -> {
                String processed = null;
                try {
                    processed = processAtte(atte);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                queue.add(processed); // 线程安全
            });
        }

        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);

        StringBuilder result = new StringBuilder();
        queue.forEach(str -> result.append(str).append(";").append("\n"));
        log.info("Final Result:\n" + result);
        long endTimeMillis = System.currentTimeMillis();

        // 计算并输出执行时间（毫秒）
        long executionTimeMillis = endTimeMillis - startTimeMillis;
        log.info("获取发票校验信息执行时间：" + executionTimeMillis);
        return Result.OK(result.toString());
    }

    /**
     * 获取附件下载信息
     *
     * @param AccessToken
     * @return
     */
    public GetFileDownloadInfoResponse getAttchmentDownloadInfo(String spaceId, String fileId, String AccessToken, Client storageClient, GetFileDownloadInfoHeaders getFileDownloadInfoHeaders, GetFileDownloadInfoRequest getFileDownloadInfoRequest) throws Exception {
        GetFileDownloadInfoResponse response = null;
        try {
            response = storageClient.getFileDownloadInfoWithOptions(spaceId, fileId, getFileDownloadInfoRequest, getFileDownloadInfoHeaders, new RuntimeOptions());
        } catch (TeaException err) {
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        } catch (Exception _err) {
            TeaException err = new TeaException(_err.getMessage(), _err);
            if (!Common.empty(err.code) && !Common.empty(err.message)) {
                // err 中含有 code 和 message 属性，可帮助开发定位问题
            }

        }
        return response;
    }

    public static Client createStorageClient() throws Exception {
        Config config = new Config();
        config.protocol = "https";
        config.regionId = "central";
        return new Client(config);
    }

    /**
     * 添加
     *
     * @param ticket
     * @return
     */
    @AutoLog(value = "进项发票-添加")
    @Operation(summary = "进项发票-添加")
    @RequiresPermissions("JTSJ:ticket:add")
    @PostMapping(value = "/add")
    public Result<String> add(@RequestBody Ticket ticket) {
        ticketService.save(ticket);
        return Result.OK("添加成功！");
    }

    /**
     * 编辑
     *
     * @param ticket
     * @return
     */
    @AutoLog(value = "进项发票-编辑")
    @Operation(summary = "进项发票-编辑")
    @RequiresPermissions("JTSJ:ticket:edit")
    @RequestMapping(value = "/edit", method = {RequestMethod.PUT, RequestMethod.POST})
    public Result<String> edit(@RequestBody Ticket ticket) {
        ticketService.updateById(ticket);
        return Result.OK("编辑成功!");
    }

    /**
     * 通过id删除
     *
     * @param id
     * @return
     */
    @AutoLog(value = "进项发票-通过id删除")
    @Operation(summary = "进项发票-通过id删除")
    @RequiresPermissions("JTSJ:ticket:delete")
    @DeleteMapping(value = "/delete")
    public Result<String> delete(@RequestParam(name = "id", required = true) String id) {
        ticketService.removeById(id);
        return Result.OK("删除成功!");
    }

    /**
     * 批量删除
     *
     * @param ids
     * @return
     */
    @AutoLog(value = "进项发票-批量删除")
    @Operation(summary = "进项发票-批量删除")
    @RequiresPermissions("JTSJ:ticket:deleteBatch")
    @DeleteMapping(value = "/deleteBatch")
    public Result<String> deleteBatch(@RequestParam(name = "ids", required = true) String ids) {
        this.ticketService.removeByIds(Arrays.asList(ids.split(",")));
        return Result.OK("批量删除成功!");
    }

    /**
     * 通过id查询
     *
     * @param id
     * @return
     */
    //@AutoLog(value = "进项发票-通过id查询")
    @Operation(summary = "进项发票-通过id查询")
    @GetMapping(value = "/queryById")
    public Result<Ticket> queryById(@RequestParam(name = "id", required = true) String id) {
        Ticket ticket = ticketService.getById(id);
        if (ticket == null) {
            return Result.error("未找到对应数据");
        }
        return Result.OK(ticket);
    }

    /**
     * 导出excel
     *
     * @param request
     * @param ticket
     */
    @RequiresPermissions("JTSJ:ticket:exportXls")
    @RequestMapping(value = "/exportXls")
    public ModelAndView exportXls(HttpServletRequest request, Ticket ticket) {
        return super.exportXls(request, ticket, Ticket.class, "进项发票");
    }

    /**
     * 通过excel导入数据
     *
     * @param request
     * @param response
     * @return
     */
    @RequiresPermissions("JTSJ:ticket:importExcel")
    @RequestMapping(value = "/importExcel", method = RequestMethod.POST)
    public Result<?> importExcel(HttpServletRequest request, HttpServletResponse response) {
        return super.importExcel(request, response, Ticket.class);
    }

}
